package moodish.Security;

import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Stack;
import moodish.comm.ServerComm;
import moodish.comm.ServerSideMessage;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;

/**
 * 
 * @author andre.silva
 *
 */
public class ServerCommFilter extends ServerCommDecorator {
	
	private Hashtable <String, Stack<MoodishTime>> moodishSendByUser; //
	private Hashtable <String, Date> blackList;
	
	private final static int FILTER1_TIME = 30;
	private final static int FILTER1_MOODSWARNING = 7;
	private final static int FILTER1_MOODSBAN = 10;
	private final static int FILTER2_TIME = 60;
	private final static int FILTER2_MOODSWARNING = 1;
	private final static int FILTER2_MOODSBAN = 3;
	private final static int BAN_TIME = 30;
	
	/**
	 * The constructor of the class 
	 * @param serverComm
	 */
	public ServerCommFilter(ServerComm serverComm) {
		super(serverComm);
		moodishSendByUser = new Hashtable<String, Stack<MoodishTime>>();
		blackList = new Hashtable<String, Date>();
	}

	/**
	 * There are no changes to the behavior of this method 
	 */
	public void start() {
		serverComm.start();
	}

	/**
	 * There are no changes to the behavior of this method  
	 */
	public ServerSideMessage getNextMessage() {
		return serverComm.getNextMessage();
	}

	/**
	 * There are no changes to the behavior of this method 
	 */
	public boolean hasNextMessage() {
		return serverComm.hasNextMessage();
	}

	/**
	 * 0 - doens't add or alter the behavior of the original method in the ServerCommDummy class
	 * 1 - alter the behavior of the method to complain with the project requirements (F2)
	 * 2 - alter the behavior of the method to complain with the project requirements (F2 and F4)
	 * 3 - alter the behavior of the method to complain with the project requirements (F3)
	 * 4 - alter the behavior of the method to complain with the project requirements (F3 and F4)
	 */
	public void sendMoodishMessage(String fromNicename, String toNickname, String moodishMessage) {
		switch(verifyAbuseBehavior(fromNicename, moodishMessage)){
		case 0 : serverComm.sendMoodishMessage(fromNicename, toNickname, moodishMessage);
			break;
		case 1 : serverComm.sendError(fromNicename, "WARNING!! You have send to many moods.");
			break;
		case 2 : serverComm.disconnectClient(fromNicename);
			break;
		case 3 : serverComm.sendError(fromNicename, "WARNING!! You have send the same mood .");
			break;
		case 4 : serverComm.disconnectClient(fromNicename);
			break;
		default: serverComm.sendMoodishMessage(fromNicename, toNickname, moodishMessage);
		}
	}

	/**
	 * There are no changes to the behavior of this method 
	 */
	public void sendNewFriendship(String toNickname, String newFriendship) {
		this.serverComm.sendNewFriendship(toNickname, newFriendship);
	}

	/**
	 * There are no changes to the behavior of this method 
	 */
	public void sendNewUnfriendship(String toNickname, String unfriendship) {
		this.serverComm.sendNewUnfriendship(toNickname, unfriendship);
	}

	/**
	 * There are no changes to the behavior of this method 
	 */
	public void sendError(String toNickname, String error) {
		this.serverComm.sendError(toNickname, error);
	}

	/**
	 * There are no changes to the behavior of this method 
	 */
	public void sendClientConnected(String toNickname, String connectedNickname) {
		this.serverComm.sendClientConnected(toNickname, connectedNickname);
	}

	/**
	 * There are no changes to the behavior of this method 
	 */
	public void sendClientDisconnected(String toNickname, String disconnectedNickname) {
		this.sendClientDisconnected(toNickname, disconnectedNickname);
	}

	/**
	 * 
	 * 
	 * 
	 */
	public boolean clientIsConnected(String nickname) {
		if(blackList.containsKey(nickname)){
			if(secondsCalc(blackList.get(nickname)) > BAN_TIME){
				blackList.remove(nickname);
				return this.serverComm.clientIsConnected(nickname);
			}else{
				return false;
			}
		}else{
			return this.serverComm.clientIsConnected(nickname);
		}
	}

	/**
	 * There are no changes to the behavior of this method 
	 */
	public void disconnectClient(String nickname) {
		this.serverComm.disconnectClient(nickname);
	}

	/**
	 * This method is responsible for evaluate the conditions specified in the requirements of the project.
	 * The main purpose is to apply rules to the users. 
	 * @return a int that represents the action to be taken (see sendMoodishMessage)
	 */	
	public int verifyAbuseBehavior(String name, final String moodishMessage){
	
		if(moodishSendByUser.containsKey(name)){
			int limitThirty = 0;
			int limitSixty = 0;
			Stack<MoodishTime> stack = (Stack<MoodishTime>)moodishSendByUser.get(name).clone();
			
			Iterator<MoodishTime> it = stack.listIterator(0);
			while(it.hasNext()){
				MoodishTime mt = it.next();
				if( secondsCalc(mt.getTimeStamp()) >= FILTER2_TIME){
					moodishSendByUser.get(name).remove(mt);
				}else{
					break;
				}
			}
			
			
			//This mechanism uses an apache library to count the number of occurrences of a certain onject inside a list/array   
			//in this case to count the number of times that a certain moodish is send repeatedly in the time frame of 60 seconds
			limitThirty = CollectionUtils.countMatches(moodishSendByUser.get(name), new Predicate<MoodishTime>(){
				@Override
				public boolean evaluate(MoodishTime mood){ 
					return (moodishMessage.equals(mood.getName()) && (secondsCalc(mood.getTimeStamp()) < FILTER2_TIME)); }
			});
			
			//This mechanism uses an apache library to count the number of occurrences of a certain onject inside a list/array 
			//in this case to count the number of times that a moodish is send in the time frame of 30 seconds
			limitSixty = CollectionUtils.countMatches(moodishSendByUser.get(name), new Predicate<MoodishTime>(){
				@Override
				public boolean evaluate(MoodishTime mood){ 
					return secondsCalc(mood.getTimeStamp()) < FILTER1_TIME; }
			});
			
			
			if(limitThirty == FILTER2_MOODSWARNING){
				moodishSendByUser.get(name).push(new MoodishTime(moodishMessage, new Date()));
				return 1;
			}else if (limitThirty == FILTER2_MOODSBAN){
				blackList.put(name, new Date()); return 2;
			}else if(limitSixty == FILTER1_MOODSWARNING){
				moodishSendByUser.get(name).push(new MoodishTime(moodishMessage, new Date()));
				return 3;
			}else if(limitSixty == FILTER1_MOODSBAN){
				blackList.put(name, new Date()); return 4;
			}else{
				moodishSendByUser.get(name).push(new MoodishTime(moodishMessage, new Date()));
				return 0;
			}
		}else{
			Stack<MoodishTime> mt = new Stack<MoodishTime>();
			mt.push(new MoodishTime(moodishMessage,new Date()));
			moodishSendByUser.put(name, mt);
			return 0;
		}
	}
	
	/**
	 * This method it's used to calculate the differences between time stamps
	 * @param moodDate
	 * @return
	 */
	private float secondsCalc(Date moodDate){
		Date date = new Date();
		float time = (moodDate.getTime() - date.getTime()) / 1000;
		return time;
	}

	/**
	 * This method its only used for tests
	 * @param moodishSendByUser
	 */
	public void setMoodishSendByUser(Hashtable<String, Stack<MoodishTime>> moodishSendByUser) {
		this.moodishSendByUser = moodishSendByUser;
	}

	/**
	 * This method its only used for tests
	 * @param blackList
	 */
	public void setBlackList(Hashtable<String, Date> blackList) {
		this.blackList = blackList;
	}
	
	/**
	 * This method its only used for tests
	 * @return blackList
	 */
	public Hashtable<String, Date> getBlackList(){
		return this.blackList;
	}
	
	/**
	 * This method its only used for tests
	 * @return moodishSendByUser
	 */
	public Hashtable<String, Stack<MoodishTime>> getMoodishSendByUser(){
		return this.moodishSendByUser;
	}
}
